package tr.com.netgis;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import tr.com.netgis.db.NGDbHelper;
import tr.com.netgis.pojo.NGLocation;
import tr.com.netgis.service.LocationListenerPolicy;
import tr.com.netgis.util.Constants;
import tr.com.netgis.util.RotatingNGLocation;
import tr.com.netgis.util.Status;
import tr.com.netgis.util.Utility;
import tr.com.netgis.web.SetLocationRequest;
import tr.com.netgis.web.SetLocationResponse;
import tr.com.netgis.web.WebServisCaller;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;

/**
 * All gps operations is executed in this service.
 * @author NetGis
 *
 */
public class MTSService extends Service {

	private RotatingNGLocation LAST_N_LOCATIONS;
	public boolean APP_STARTED = false;
	private Set<String> mTrackedProviders;
	private List<NGLocationListener> mListeners;
	
	public RotatingNGLocation getLastNLocations (){
		return LAST_N_LOCATIONS;
	}
	
	public NGLocation getLastNGLocation (){
		return LAST_N_LOCATIONS.getLastInstertedElement();
	}
	
	private final Object latestSearchResultLock = new Object();
	private MTSServiceResult latestResult = new MTSServiceResult();
	private List<IMTSServiceListener> listeners = new ArrayList<IMTSServiceListener>();
	private static final String TAG = MTSService.class.getSimpleName();
	private static final String TAG_LS = "LOCATION SERVICE";
	private Timer timer;
	
	public LocationManager locationManager;
	public String locationManagerProvider = "unknown";
	public int locationManagerProviderStatus = -1;
	
	/**
	 * last changed location
	 */
	private Location lastLocation;

	private LocationListener locationListener;
	
	/**
	 * It is set on onLocationChange and in initialization of location manager by lastKnowLocation
	 * 
	 * @return
	 */
	public Location getLastLocation (){
		return lastLocation;
	}
	
	public String getLocationManagerProvider (){
		return this.locationManagerProvider;
	}
	
	public String getLocationManageProviderStatus() {
		switch (this.locationManagerProviderStatus) {
		case LocationProvider.AVAILABLE:
			return "AVAILABLE";
		case LocationProvider.OUT_OF_SERVICE:
			return "OUT_OF_SERVICE";
		case LocationProvider.TEMPORARILY_UNAVAILABLE:
			return "TEMPORARILY_UNAVAILABLE";
		default:
			return "BILINMEYEN";
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		Log.d(TAG, "Boundinnnnggg " + intent);
		if (MTSService.class.getName().equals(intent.getAction())) {
			Log.d(TAG, "Bound by intent " + intent);
			return apiEndpoint;
		} else {
			return null;
		}
	}
	
	

	@Override
	public boolean onUnbind(Intent intent) {
		Log.d(TAG, "Unbindddddd " + intent);
		return super.onUnbind(intent);
	}

	@Override
	public void onCreate() {
		super.onCreate();
		Log.i(TAG, "Service creating");
		
		LAST_N_LOCATIONS = new RotatingNGLocation(Constants.LAST_N_LOCATIONS_SIZE);
		
		buildAlertMessageNoGps();
		
		IntentFilter intentToReceiveFilter = new IntentFilter();
		intentToReceiveFilter.addAction("android.location.PROVIDERS_CHANGED");
		this.registerReceiver(mIntentReceiver, intentToReceiveFilter);
		mReceiversRegistered = true;
		
		//locationListener = new NGLocationListener(this);
		// Get the location manager
		this.locationManager = getLocationManager();
		//this.locationManager.addGpsStatusListener(this);
		
		refresh(true);
        //showNotification("Mobiz", "Mobil İzleme Başladı");		
        inititializeDB();
        NGLocation location = sendApplicationStatusToServer(Status.UYGULAMA_ACILDI);
        updateUI(location);
        //mHelper.deleteLocations(NGLocation.FORLOG);
	}
	

	@Override
	public boolean stopService(Intent name) {
		// TODO Auto-generated method stub
		return super.stopService(name);
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
		Log.i(TAG, "Service destroying");
		timer.cancel();
		timer = null;
		locationManager.removeUpdates(locationListener);
        //mHelper.deleteLocations(NGLocation.FORLOG);
		mHelper.close();
		
		if(mReceiversRegistered) {
			this.unregisterReceiver(mIntentReceiver);
			mReceiversRegistered = false;
		}
		
		sendApplicationStatusToServer(Status.UYGULAMA_KAPANDI);
	}

    public static final int MSG_MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1;
    public static final int MSG_MINIMUM_TIME_BETWEEN_UPDATES = 2;
    public static final int MSG_GET_LAST_LOCATION = 3;
    
    private NGLocation sendApplicationStatusToServer(Status status) {
       	NGLocation location = new NGLocation();
		try {
			long time = new Date().getTime();
			location.setDate(time);
			location.setTarihSaat(time);
			location.setStatus(status);
			SharedPrefs prefs = SharedPrefs.getInstance();
			SetLocationRequest request = new SetLocationRequest();
			request.setLocation(location);
			request.setServerUrl(prefs.getServerUrl());		
			SetLocationResponse response = WebServisCaller.callWebService(request);
			FileLogger.log(this, response);	
			broadcast(location);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return location;
    }
    
    /*
    private void sendApplicationDestroyToServer (){
    	NGLocation lastNGLocation = LAST_N_LOCATIONS.getLastInstertedElement();
		if (lastNGLocation != null) {
			try {
				lastNGLocation.setDate(new Date().getTime());
				lastNGLocation.calculateSpeedAndStatus(LAST_N_LOCATIONS);
				lastNGLocation.setStatus(Status.UYGULAMA_KAPANDI);
				SharedPrefs prefs = SharedPrefs.getInstance();
				SetLocationRequest request = new SetLocationRequest();
				request.setLocation(lastNGLocation);
				request.setServerUrl(prefs.getServerUrl());		
				SetLocationResponse response = WebServisCaller.callWebService(request);
				FileLogger.log(this, response);	
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
    }
    */
    
    
	/**
	 * 
	 * @author NetGis
	 *
	 */
	class UpdateTask extends TimerTask {
		
		private MTSService setLocationService;
		
		public UpdateTask(MTSService service) {
			super();
			this.setLocationService = service;
		}

		@Override
		public void run() {
			Log.i(TAG, "Timer Task --> MTS service working");
			try {
				MTSServiceResult newSearchResult = new MTSServiceResult();
				newSearchResult.setmData(formMData(setLocationService));
				NGLocation lastNGLocation = LAST_N_LOCATIONS.getLastInstertedElement();
				Log.d(TAG, "" + LAST_N_LOCATIONS.toList().size());
				if (lastNGLocation == null) {
					Log.d(TAG, "Last NGLocation null");
				} else if (true/*!lastNGLocation.isSent()*/) {
					Log.d(TAG, "Last Element: " + lastNGLocation);
					lastNGLocation.setDate(new Date().getTime());
					newSearchResult.add(lastNGLocation);
					newSearchResult.setAbout(setLocationService.about.toString());
					
					SharedPrefs prefs = SharedPrefs.getInstance();
					
					if (lastNGLocation.needToBeSent() && prefs.isSendToServer()) {
						SetLocationRequest request = new SetLocationRequest();
						request.setLocation(lastNGLocation);
						request.setServerUrl(prefs.getServerUrl());					
						SetLocationResponse response = WebServisCaller.callWebService(request);
						broadcast(lastNGLocation);
						FileLogger.log(setLocationService, response);
						Log.i(TAG_LS + "Response-->", response.toString());
					} else {
						Log.d(TAG, "Last Location already sent: " + lastNGLocation);						
					}
				}
				
				/*
				synchronized (latestSearchResultLock) {
					latestResult = newSearchResult;
				}
			
				synchronized (listeners) {
					for (IMTSServiceListener listener : listeners) {
						try {
							listener.handleLocationsUpdated();
						} catch (RemoteException e) {
							Log.w(TAG, "Failed to notify listener " + listener, e);
						}
					}
				}
				*/
				
			} catch (Throwable t) {
				Log.e(TAG, "Failed to retrieve", t);
			}						
		}
	}
	
	private void broadcast (NGLocation location){
		Intent intent = new Intent("tr.com.netgis.UPDATE_UI");
		intent.putExtra("location", location);
		sendBroadcast(intent);
	}
	
	private IMTSServiceApi.Stub apiEndpoint = new IMTSServiceApi.Stub() {

		@Override
		public MTSServiceResult getResult() throws RemoteException {
			return latestResult;
		}

		@Override
		public void addListener(IMTSServiceListener listener)
				throws RemoteException {
			synchronized (listeners) {
				listeners.add(listener);
				Log.i(TAG, "Add to Service (Listener): " + listener);
			}
		}

		@Override
		public void removeListener(IMTSServiceListener listener)
				throws RemoteException {
			synchronized (listeners) {
				listeners.remove(listener);
				Log.i(TAG, "Removed from Service (Listener): " + listener);
			}
		}

		@Override
		public void sendCommand(int command) throws RemoteException {
			Log.i(TAG, "Command Sent to Service: " + command);
			if (command == MSG_MINIMUM_DISTANCE_CHANGE_FOR_UPDATES) {
				new Thread(new Runnable() {
					
					@Override
					public void run() {
						Looper.prepare();
						refresh(false);
						Looper.loop();
						Looper.getMainLooper().quit();
					}
				}).start();				
			} else if (command == MSG_GET_LAST_LOCATION) {
				Toast.makeText(getApplicationContext(), "xxxx", 2000);
			}
		}

		@Override
		public boolean isRecording() throws RemoteException {
			// TODO Auto-generated method stub
			return true;
		}
	};

	private static String formMData (MTSService setLocationService){
		return Utility.createSimpleDateFormat().format(new Date()) + 
				"\n" + setLocationService.getLocationManagerProvider() + 
				": " + setLocationService.getLocationManageProviderStatus();
	}
	
	private synchronized void updateUI (NGLocation location){
		MTSServiceResult newSearchResult = new MTSServiceResult();
		newSearchResult.setmData(formMData(this));
		newSearchResult.add(location);
		synchronized (latestSearchResultLock) {
			latestResult = newSearchResult;
		}
	
		synchronized (listeners) {
			for (IMTSServiceListener listener : listeners) {
				try {
					listener.handleLocationsUpdated();
				} catch (RemoteException e) {
					Log.w(TAG, "Failed to notify listener " + listener, e);
				}
			}
		}				
	}
	
	class NGLocationListener implements LocationListener {
		
		MTSService mtsService;
		
		public NGLocationListener(MTSService mtsService) {
			this.mtsService = mtsService;
		}
		
		@Override
		public void onLocationChanged(Location location) {
			Log.i("CHANGED", location.toString());
			NGLocation ngLocation = new NGLocation(location);
			ngLocation.calculateSpeedAndStatus(LAST_N_LOCATIONS);
			lastLocation = location;
			LAST_N_LOCATIONS.insertElement(ngLocation);
			Log.e("CHANGED", LAST_N_LOCATIONS.toList().size() + "");
			Log.e("CHANGED", ngLocation.toString());
	
			/*
			if (Status.UYGULAMA_ACILDI.equals(ngLocation.getStatus())) {
				//Üretilen konumun ekranda gösterilmesi
				SetLocationResult newSearchResult = new SetLocationResult();
				newSearchResult.setmData(formMData(setLocationService));
				NGLocation lastNGLocation = LAST_N_LOCATIONS.getLastInstertedElement();
				lastNGLocation.setDate(new Date().getTime());
				newSearchResult.add(lastNGLocation);
				synchronized (latestSearchResultLock) {
					latestResult = newSearchResult;
				}
			
				synchronized (listeners) {
					for (SetLocationListener listener : listeners) {
						try {
							listener.handleLocationsUpdated();
						} catch (RemoteException e) {
							Log.w(TAG, "Failed to notify listener " + listener, e);
						}
					}
				}				
			}
			*/
		}	
		
		@Override
		public void onProviderEnabled(String provider) {
			Log.i(TAG_LS, "Enabled new provider " + provider);
			refresh(false);

			/*
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Looper.prepare();
					refresh(false);
					Looper.loop();
					Looper.getMainLooper().quit();
				}
			}).start();
			*/
		}

		@Override
		public void onProviderDisabled(String provider) {
			Log.i(TAG_LS, "Disabled provider " + provider);
			refresh(false);

			/*
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					Looper.prepare();
					refresh(false);
					Looper.loop();
					Looper.getMainLooper().quit();
				}
			}).start();
			*/
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			Log.i(TAG_LS, "Provider status changed : " + status);
			mtsService.locationManagerProviderStatus = status;
		}		
	}
	
	private StringBuffer about = new StringBuffer();
	
	private void initializeLocationManager () {

		// Define the criteria how to select the locatioin provider -> use
		// default
		
		/*
		Criteria criteria = new Criteria(); 
		criteria.setAccuracy(Criteria.ACCURACY_FINE); 
		criteria.setAltitudeRequired(false); 
		criteria.setBearingRequired(false); 
		criteria.setCostAllowed(true); 
		criteria.setPowerRequirement(Criteria.POWER_HIGH); 
		
		this.locationManagerProvider = this.locationManager.getBestProvider(criteria, true);
		FileLogger.log("Best Provider: " + this.locationManagerProvider);
		FileLogger.log("Use GPS: " + this.useGPS());
		
		*/
		LocationListenerPolicy policy = SharedPrefs.getPolicy();

		/*
        mTrackedProviders = getTrackedProviders();
        List<String> locationProviders = this.locationManager.getAllProviders();
        mListeners = new ArrayList<NGLocationListener>(locationProviders.size());
		for (String providerName : locationProviders) {
            if (mTrackedProviders.contains(providerName)) {
                Log.d(TAG_LS, "Adding location listener for provider " + providerName);
                NGLocationListener listener = new NGLocationListener(this);
                this.locationManager.requestLocationUpdates(providerName, policy.getMinTime(),
                        policy.getMinDistance(), listener);
                mListeners.add(listener);
            }
        }		
		*/
		
		if (this.useGPS() && this.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) && Utility.isGPSEnabled(this)) {
			this.locationManagerProvider = LocationManager.GPS_PROVIDER;
		} else if (this.locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
			this.locationManagerProvider = LocationManager.NETWORK_PROVIDER;			
		}  else if (this.locationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)) {
			this.locationManagerProvider = LocationManager.PASSIVE_PROVIDER;
		} else {			
			this.locationManagerProvider = LocationManager.GPS_PROVIDER;
		}
		
		
		FileLogger.log("Selected Provider: " + this.locationManagerProvider);
		
		this.locationListener = new NGLocationListener(this);
		//this.locationManager.addGpsStatusListener(this);
		this.locationManager.requestLocationUpdates(this.locationManagerProvider, 
				policy.getMinTime(), 
				policy.getMinDistance(), 
				locationListener);		
		

		this.about = new StringBuffer();
		this.about.append("\nProvider: " + this.locationManagerProvider);
		this.about.append("\nLocation Manager initialized");
		
		Log.d(TAG_LS, this.about.toString());
		//lastLocation = locationManager.getLastKnownLocation(this.locationManagerProvider);
	}
	
	private Set<String> getTrackedProviders() {
        Set<String> providerSet = new HashSet<String>();
        providerSet.add(LocationManager.GPS_PROVIDER);
        providerSet.add(LocationManager.NETWORK_PROVIDER);
        return providerSet;
    }

	@Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "Received start id " + startId + ": " + intent);
        super.onStartCommand(intent, flags, startId);
        /*
		SetLocationResult newSearchResult = new SetLocationResult();
		newSearchResult.setLocations(LAST_N_LOCATIONS.toList());
		Log.d(TAG, "LAST_N_LOC size: " + newSearchResult.getLocations().size());
		synchronized (latestSearchResultLock) {
			latestResult = newSearchResult;
		}
	
		synchronized (listeners) {
			for (SetLocationListener listener : listeners) {
				try {
					if (listener.asBinder().pingBinder())
						listener.handleLocationsUpdated();
				} catch (RemoteException e) {
					Log.w(TAG, "Failed to notify listener " + listener, e);
				}
			}
		}        
        */
        return START_STICKY;
    }

	private boolean useGPS () {
		SharedPrefs prefs = SharedPrefs.getInstance();
		boolean useGPS = prefs.useGPS();
	    Log.d(TAG, "*** Use GPS: " + useGPS);				
	    return useGPS;
	}
	
	private void initializeTimer (){
		SharedPrefs prefs = SharedPrefs.getInstance();
		long period = prefs.getSendFrequency();
		Log.i(TAG, "Period: " + Long.toString(period));
		if (timer != null) {
			timer.cancel();
			timer = null;
		}
		timer = new Timer("SetLocationTimer " + UUID.randomUUID().toString());
		timer.schedule(new UpdateTask(this), 2000L, period);		
	}
	
	private void refresh (boolean initialize){
		
		if (!initialize) {
			this.locationManager.removeUpdates(this.locationListener);
			this.locationListener = null;
		}
		initializeLocationManager();	
		initializeTimer();
	}
	
	NGDbHelper mHelper;
	
	private void inititializeDB (){
		mHelper = new NGDbHelper(this);	
	}
	
	private void buildAlertMessageNoGps() {
		if (SharedPrefs.getInstance().useGPS() && !Utility.isGPSEnabled(getApplicationContext())) {
			//NGApplication.showNotification(Constants.APP_NAME, "GPS aktif değil, aktifleştirmeniz konum doğruluğunu arttırır.");
		}
		
		/*
		if (NetMobileLocationSharedPrefs.getInstance().useGPS() && !Utility.isGPSEnabled(getApplicationContext())) {
		    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
		    String message = "GPS aktif değil, aktifleştirmek ister misiniz?";
		    builder.setMessage(message).setCancelable(false).setPositiveButton("Evet",
	            new DialogInterface.OnClickListener() {
	                public void onClick(
	                        final DialogInterface dialog,
	                        final int id) {
	                    Intent intent = new Intent(
	                            Settings.ACTION_LOCATION_SOURCE_SETTINGS);
	                    startActivity(intent);
	                }
	            }).setNegativeButton("Hayır",
	            new DialogInterface.OnClickListener() {
	                public void onClick(final DialogInterface dialog,
	                        final int id) {
	                    dialog.cancel();
	                }
	            });
		    final AlertDialog alert = builder.create();
		    alert.show();			
		}
		*/
	}
	
	boolean mReceiversRegistered = false;
	private final ProviderChangedBroadcastReceiver mIntentReceiver = new ProviderChangedBroadcastReceiver(this, new Runnable(){
		@Override
		public void run() {
			Log.d(TAG_LS, "GPS----> " + mIntentReceiver.isGPSEnabled());
			synchronized (MTSService.this) {
				
			}
			/*
			if (mIntentReceiver.isGPSEnabled()) {
				refresh(false);
			}
			*/
		}		
	});
	
	/**
    * De-registers all location listeners, closes persistent storage
    */
   protected synchronized void stopListeners() {
       LocationManager lm = getLocationManager();
       if (mListeners != null) {
           for (NGLocationListener listener : mListeners) {
               lm.removeUpdates(listener);
           }
           mListeners.clear();
       }
       mListeners = null;
   }
   
   private LocationManager getLocationManager() {
       return (LocationManager) getSystemService(Context.LOCATION_SERVICE);
   }

   /*
	@Override
	public void onGpsStatusChanged(int event) {
		switch (event) {
        case GpsStatus.GPS_EVENT_STARTED:
        	Log.d(TAG, "GPS started");
			//refresh(false);
            break;
        case GpsStatus.GPS_EVENT_STOPPED:
        	Log.d(TAG, "GPS stopped");
			//refresh(false);
            break;
        default:
        	Log.d(TAG, String.format("GPS status change even: %d", event));
        	break;
		}
	}	
	*/
	
}
